using System.IO;

namespace Ptx2bmpConverter
{
    /// <summary>
    /// This is A DDS-Surface Class
    /// </summary>
    public class DDSFile
    {
        #region Internal Fields

        /// <summary>
        /// This is the DDS-Header
        /// </summary>
        internal DDSURFACEDESC2 header;

        #endregion Internal Fields

        #region Private Fields

        /// <summary>
        /// This is the Data which stores the Pixels and etc??
        /// </summary>
        private byte[] bdata;

        /// <summary>
        /// This are the Magic Bytes at the start of an DDS
        /// </summary>
        private char[] dwMagic = { 'D', 'D', 'S', ' ' };

        #endregion Private Fields

        #region Public Constructors

        /// <summary>
        /// Creates a new DDS File
        /// </summary>
        /// <param name="height">Height of the DDS Surface</param>
        /// <param name="width">Width of the dds Surface</param>
        /// <param name="nMipMaps"> Number of MipMaps</param>
        /// <param name="ddsType">Type of DDS.</param>
        public DDSFile(int height, int width, int nMipMaps, DDSType ddsType)
        {
            //Header
            header = new DDSURFACEDESC2(height, width, nMipMaps, ddsType);
            bdata = new byte[height * width * 4];
            for (int i = 0; i < bdata.Length; i += 4)
            {
                bdata[i] = 255;
                bdata[i + 1] = 255;
                bdata[i + 2] = 255;
                bdata[i + 3] = 255;
            }
        }

        #endregion Public Constructors

        #region Public Enums

        /// <summary>
        /// DDS Can have differnet Ways to store the data. This are they
        /// </summary>
        public enum DDSType : int
        {
            /// <summary>
            /// This is used when the DDS should Store the Data uncompressed
            /// </summary>
            Uncompressed = 0x00000000,

            DXT1 = 0x31545844,// 44585431,
            DXT3 = 0x33545844,// 44585433,
            DXT5 = 0x35545844,// 44585435
        }

        #endregion Public Enums

        #region Internal Enums

        internal enum DdsCaps
        {
            /// <summary>
            /// Optional; must be used on any file that contains more than one surface (a mipmap,
            /// a cubic environment map, or mipmapped volume texture).
            /// </summary>
            DDSCAPS_COMPLEX = 0x8,

            /// <summary>
            /// Optional; should be used for a mipmap.
            /// </summary>
            DDSCAPS_MIPMAP = 0x400000,

            /// <summary>
            /// Required
            /// </summary>
            DDSCAPS_TEXTURE = 0x1000
        }

        internal enum DdsCaps2
        {
            DDSCAPS2_CUBEMAP = 0x200,
            DDSCAPS2_CUBEMAP_POSITIVEX = 0x400,
            DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800,
            DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000,
            DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000,
            DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000,
            DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000,
            DDSCAPS2_VOLUME = 0x200000
        }

        internal enum DdsPixelformatFlags
        {
            /// <summary>
            /// Texture contains alpha data; dwRGBAlphaBitMask contains valid data.
            /// </summary>
            DDPF_ALPHAPIXELS = 0x1,

            /// <summary>
            /// Used in some older DDS files for alpha channel only uncompressed data
            /// (dwRGBBitCount contains the alpha channel bitcount; dwABitMask contains valid data)
            /// </summary>
            DDPF_ALPHA = 0x2,

            /// <summary>
            /// Texture contains compressed RGB data; dwFourCC contains valid data.
            /// </summary>
            DDPF_FOURCC = 0x4,

            /// <summary>
            /// Texture contains uncompressed RGB data; dwRGBBitCount and the RGB masks
            /// (dwRBitMask, dwRBitMask, dwRBitMask) contain valid data.
            /// </summary>
            DDPF_RGB = 0x40,

            /// <summary>
            /// Used in some older DDS files for YUV uncompressed data
            /// (dwRGBBitCount contains the YUV bit count; dwRBitMask contains
            /// the Y mask, dwGBitMask contains the U mask, dwBBitMask contains the V mask)
            /// </summary>
            DDPF_YUV = 0x200,

            /// <summary>
            /// Used in some older DDS files for single channel color uncompressed data
            /// (dwRGBBitCount contains the luminance channel bit count; dwRBitMask contains
            /// the channel mask). Can be combined with DDPF_ALPHAPIXELS for a two channel DDS file.
            /// </summary>
            DDPF_LUMINANCE = 0x20000
        }

        #endregion Internal Enums

        #region Public Methods

        public byte[] GetData()
        {
            return bdata;
        }

        /// <summary>
        /// Outputs the Data as a Stream
        /// </summary>
        /// <returns>Returns a Stream which can used in other classes</returns>
        public Stream ToStream()
        {
            //SharpDX.DataStream stream = new SharpDX.DataStream(128 + bdata.Length,true,true);
            //BinaryWriter bw = new BinaryWriter(stream);
            //bw.Write(dwMagic);
            //header.SaveToStream(ref bw);
            //bw.Write(0);
            //bw.Write(bdata);
            //stream.Position = 0;
            //return stream;

            MemoryStream stream = new MemoryStream(128 + bdata.Length);
            BinaryWriter bw = new BinaryWriter(stream);
            bw.Write(dwMagic);
            header.SaveToStream(ref bw);
            bw.Write(0);
            bw.Write(bdata);
            stream.Position = 0;
            return stream;
        }

        #endregion Public Methods

        #region Internal Methods

        internal void Dispose()
        {
            this.bdata = null;
        }

        internal void SetDataFromPTXdata(System.IO.BinaryReader br, int nMipMaps)
        {
            int offset = 0;
            this.bdata = new byte[br.BaseStream.Length - br.BaseStream.Position - (nMipMaps * 4)];
            for (int i = 0; i < nMipMaps; i++)
            {
                byte[] temp = new byte[br.ReadInt64()];
                temp = br.ReadBytes(temp.Length);
                temp.CopyTo(bdata, offset);
                offset += temp.Length;
            }
        }

        #endregion Internal Methods

        #region Internal Structs

        internal struct DDPIXELFORMAT
        {
            #region Internal Fields

            internal int alphaBitMask;
            internal int bBitMask;
            internal DdsPixelformatFlags flags;
            internal int fourCC;
            internal int gBitMask;
            internal int rBitMask;
            internal int RGBBitCount;
            internal int size;

            #endregion Internal Fields

            #region Public Constructors

            public DDPIXELFORMAT(DDSType ddsType)
            {
                this.size = 32;

                this.flags = 0;
                switch (ddsType)
                {
                    case DDSType.Uncompressed:
                        this.flags = DdsPixelformatFlags.DDPF_RGB;
                        this.RGBBitCount = 32;
                        this.fourCC = 0;
                        this.rBitMask = 8;
                        this.gBitMask = 8;
                        this.bBitMask = 8;
                        this.alphaBitMask = 8;
                        break;

                    default:
                        this.flags = DdsPixelformatFlags.DDPF_FOURCC;
                        this.RGBBitCount = 0;
                        this.fourCC = (int)ddsType;
                        this.rBitMask = 0;
                        this.gBitMask = 0;
                        this.bBitMask = 0;
                        this.alphaBitMask = 0;
                        break;
                }
            }

            #endregion Public Constructors

            #region Internal Methods

            internal void SaveToStream(ref BinaryWriter bw)
            {
                bw.Write(size);
                bw.Write((int)flags);
                bw.Write(fourCC);
                bw.Write(RGBBitCount);
                bw.Write(rBitMask);
                bw.Write(gBitMask);
                bw.Write(bBitMask);
                bw.Write(alphaBitMask);
            }

            #endregion Internal Methods
        }

        internal struct DDSCAPS2
        {
            #region Private Fields

            private DdsCaps caps1;
            private DdsCaps2 caps2;
            private int reserved1;
            private int reserved2;

            #endregion Private Fields

            #region Public Constructors

            public DDSCAPS2(int nMipmaps)
            {
                caps1 = 0;
                if (nMipmaps > 0)
                {
                    caps1 = DdsCaps.DDSCAPS_MIPMAP;
                }
                caps1 |= DdsCaps.DDSCAPS_TEXTURE; // Texture

                caps2 = 0;
                reserved1 = 0;
                reserved2 = 0;
            }

            #endregion Public Constructors

            #region Internal Methods

            internal void SaveToStream(ref BinaryWriter bw)
            {
                bw.Write((int)caps1);
                bw.Write((int)caps2);
                bw.Write(reserved1);
                bw.Write(reserved2);
            }

            #endregion Internal Methods
        }

        internal struct DDSURFACEDESC2
        {
            #region Internal Fields

            /// <summary>
            /// the used Pixelformat.
            /// </summary>
            internal DDPIXELFORMAT ddpfPixelFormat;

            internal DDSCAPS2 ddsCaps;

            /// <summary>
            /// The depth, in pixels, of the surface if it is a volume texture.
            /// </summary>
            internal int depth;

            internal DwFlags flags;

            /// <summary>
            /// Surface height (in pixels).
            /// </summary>
            internal int height;

            /// <summary>
            /// Number of mipmap levels, otherwise unused.
            /// </summary>
            internal int mipMapCount;

            /// <summary>
            /// The pitch or number of bytes per scan line in an uncompressed texture; the total
            /// number of bytes in the top level texture for a compressed texture.
            /// For information about how to compute the pitch, see the DDS File Layout
            /// section of the Programming Guide for DDS.
            /// </summary>
            internal int pitchOrLinearSize;

            internal int size;

            /// <summary>
            /// Surface width (in pixels).
            /// </summary>
            internal int width;

            #endregion Internal Fields

            #region Private Fields

            /// <summary>
            /// Unused.
            /// </summary>
            private int[] reserved1;

            /// <summary>
            /// Unused.
            /// </summary>
            private int reserved2;

            #endregion Private Fields

            #region Internal Constructors

            internal DDSURFACEDESC2(int height, int width, int nMipMaps, DDSType ddsType)
            {
                this.size = 124; // Static size of the header. Must be 124.
                this.flags = DwFlags.DDSD_PIXELFORMAT | DwFlags.DDSD_WIDTH | DwFlags.DDSD_HEIGHT | DwFlags.DDSD_CAPS | DwFlags.DDSD_MIPMAPCOUNT;
                this.height = height;
                this.width = width;

                switch (ddsType)
                {
                    case DDSType.Uncompressed:
                        this.depth = 0;
                        this.pitchOrLinearSize = ((width + 1) >> 1) * 4;
                        this.flags |= DwFlags.DDSD_PITCH;
                        break;

                    default:
                        this.depth = 0;
                        this.pitchOrLinearSize = 0;
                        break;
                }
                this.mipMapCount = nMipMaps;
                this.reserved1 = new int[11];
                this.ddpfPixelFormat = new DDPIXELFORMAT(ddsType);
                this.ddsCaps = new DDSCAPS2(nMipMaps);
                this.reserved2 = 0;
            }

            #endregion Internal Constructors

            #region Internal Enums

            internal enum DwFlags
            {
                DDSD_CAPS = 0x1, // 1
                DDSD_HEIGHT = 0x2, // 2
                DDSD_WIDTH = 0x4, // 4
                DDSD_PITCH = 0x8, // 8
                DDSD_PIXELFORMAT = 0x1000, // 4096
                DDSD_MIPMAPCOUNT = 0x20000, // 131072
                DDSD_LINEARSIZE = 0x80000,
                DDSD_DEPTH = 0x800000
            }

            #endregion Internal Enums

            #region Private Enums

            private enum DdsPixelformat
            {
                /// <summary>
                /// Texture contains alpha data; dwRGBAlphaBitMask contains valid data.
                /// </summary>
                DDPF_ALPHAPIXELS = 0x1,

                /// <summary>
                /// Used in some older DDS files for alpha channel only uncompressed data (dwRGBBitCount
                /// contains the alpha channel bitcount; dwABitMask contains valid data)
                /// </summary>
                DDPF_ALPHA = 0x2,

                /// <summary>
                /// Texture contains compressed RGB data; dwFourCC contains valid data.
                /// </summary>
                DDPF_FOURCC = 0x4,

                /// <summary>
                /// Texture contains uncompressed RGB data; dwRGBBitCount and the RGB masks
                /// (dwRBitMask, dwRBitMask, dwRBitMask) contain valid data.
                /// </summary>
                DDPF_RGB = 0x40,

                /// <summary>
                /// Used in some older DDS files for YUV uncompressed data (dwRGBBitCount contains
                /// the YUV bit count; dwRBitMask contains the Y mask, dwGBitMask contains the
                /// U mask, dwBBitMask contains the V mask)
                /// </summary>
                DDPF_YUV = 0x200,

                /// <summary>
                /// Used in some older DDS files for single channel color uncompressed data
                /// (dwRGBBitCount contains the luminance channel bit count; dwRBitMask contains
                /// the channel mask). Can be combined with DDPF_ALPHAPIXELS for a two channel DDS file.
                /// </summary>
                DDPF_LUMINANCE = 0x20000
            }

            #endregion Private Enums

            #region Internal Methods

            // typedef struct  {
            //    DWORD dwSize;
            //    DWORD dwFlags;
            //    DWORD dwHeight;
            //    DWORD dwWidth;
            //    DWORD dwPitchOrLinearSize;
            //    DWORD dwDepth;
            //    DWORD dwMipMapCount;
            //    DWORD dwReserved1[11];
            //    DDPIXELFORMAT ddpfPixelFormat;
            //    DDSCAPS2 ddsCaps;
            //    DWORD dwReserved2;
            // } DDSURFACEDESC2 (Direct3D 9);
            internal void SaveToStream(ref BinaryWriter bw)
            {
                bw.Write(size);
                bw.Write((int)flags);
                bw.Write(height);
                bw.Write(width);
                bw.Write(pitchOrLinearSize);
                bw.Write(depth);
                bw.Write(mipMapCount);

                for (int i = 0; i < reserved1.Length; i++)
                {
                    bw.Write(reserved1[i]);
                }

                ddpfPixelFormat.SaveToStream(ref bw);
                ddsCaps.SaveToStream(ref bw);
            }

            #endregion Internal Methods
        }

        #endregion Internal Structs
    }
}